{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b1cd770b",
   "metadata": {},
   "source": [
    "### Cifar10 dataset loader\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "6876b80f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Files already downloaded and verified\n",
      "Files already downloaded and verified\n",
      "cifar10: 45000, 5000, 10000, torch.Size([3, 32, 32])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets, transforms\n",
    "\n",
    "\n",
    "def get_loaders(\n",
    "    source, batch_size, transform, eval_transform=None, root=\"data\", split_ratio=0.1\n",
    "):\n",
    "    if eval_transform is None:\n",
    "        eval_transform = transform\n",
    "\n",
    "    trainset = source(\n",
    "        root=root,\n",
    "        train=True,\n",
    "        download=True,\n",
    "        transform=transform,\n",
    "    )\n",
    "    testset = source(\n",
    "        root=root,\n",
    "        train=False,\n",
    "        download=True,\n",
    "        transform=eval_transform,\n",
    "    )\n",
    "\n",
    "    trainset, valset = torch.utils.data.random_split(\n",
    "        trainset,\n",
    "        [int((1 - split_ratio) * len(trainset)), int(split_ratio * len(trainset))],\n",
    "    )\n",
    "\n",
    "    trainloader = DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=2)\n",
    "    valloader = DataLoader(valset, batch_size=batch_size, shuffle=True, num_workers=2)\n",
    "    testloader = DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=2)\n",
    "    return trainloader, valloader, testloader\n",
    "\n",
    "\n",
    "def get_cifar10_loaders(batch_size, root=\"data/cifar10\", split_ratio=0.1):\n",
    "    transform = transforms.Compose(\n",
    "        [\n",
    "            transforms.RandomCrop(32, padding=4),\n",
    "            transforms.RandomHorizontalFlip(),\n",
    "            transforms.ToTensor(),\n",
    "            transforms.Normalize((0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261)),\n",
    "            \n",
    "            transforms.RandomRotation(degrees=15),                     # Small random rotation\n",
    "            transforms.RandomErasing(p=0.3, scale=(0.02, 0.1))         # Random erasing augmentation\n",
    "        ]\n",
    "    )\n",
    "    eval_transform = transforms.Compose(\n",
    "        [\n",
    "            transforms.ToTensor(),\n",
    "            transforms.Normalize((0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261)),\n",
    "        ]\n",
    "    )\n",
    "    return get_loaders(\n",
    "        datasets.CIFAR10,\n",
    "        batch_size,\n",
    "        transform,\n",
    "        eval_transform=eval_transform,\n",
    "        root=root,\n",
    "        split_ratio=split_ratio,\n",
    "    )\n",
    "\n",
    "\n",
    "DATALOADERS = {\n",
    "    \"cifar10\": get_cifar10_loaders,\n",
    "}\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    datasets_to_load = [\"cifar10\"]\n",
    "    for dataset in datasets_to_load:\n",
    "        trainloader, valloader, testloader = DATALOADERS[dataset](batch_size=64)\n",
    "        print(f'{dataset}: {len(trainloader.dataset)}, {len(valloader.dataset)}, {len(testloader.dataset)}, {trainloader.dataset[0][0].shape}')\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40e4d88b",
   "metadata": {},
   "source": [
    "#### Useful utilities\n",
    "- ``evaluate(model, loader, criterion, device)`` – Evaluates a model on a dataset, computing loss, accuracy, and confusion matrix.\n",
    "- ``plot_loss_accuracy(train_loss, train_acc, val_loss, val_acc, filename)`` – Plots and saves the training and validation loss/accuracy curves.\n",
    "- ``save_model(model, filename, verbose, existed)`` – Saves a PyTorch model's state dictionary while handling filename conflicts.\n",
    "- ``load_model(model, filename, qconfig, fuse_modules, verbose)`` – Loads a saved model, optionally applying quantization and module fusion.\n",
    "- ``reset_seed(seed)`` – Sets seeds for PyTorch and NumPy to ensure reproducibility.\n",
    "- ``plot_confusion_matrix(conf_matrix, filename)`` – Generates and saves a heatmap of the confusion matrix for CIFAR-10 classification.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "75136aa8",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import numpy as np\n",
    "from sklearn.metrics import confusion_matrix\n",
    "import torch\n",
    "import torch.ao.quantization as tq\n",
    "from tqdm.notebook import tqdm\n",
    "\n",
    "DEFAULT_DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "\n",
    "\n",
    "def evaluate(model, loader, criterion, device=DEFAULT_DEVICE):\n",
    "    running_loss = 0\n",
    "    total, correct = 0, 0\n",
    "    all_preds, all_labels = [], []\n",
    "\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        loop = tqdm(loader, desc=\"Evaluating\", leave=True)\n",
    "\n",
    "        for images, labels in loop:\n",
    "            images, labels = images.to(device), labels.to(device)\n",
    "            output = model(images)\n",
    "            loss = criterion(output, labels)\n",
    "\n",
    "            running_loss += loss.item()\n",
    "            predicted = torch.argmax(output, 1)\n",
    "            total += labels.size(0)\n",
    "            correct += (predicted == labels).sum().item()\n",
    "\n",
    "            all_preds.extend(predicted.cpu().numpy())\n",
    "            all_labels.extend(labels.cpu().numpy())\n",
    "\n",
    "        loop.set_postfix(\n",
    "            loss=running_loss / (total / images.shape[0]), accuracy=correct / total\n",
    "        )\n",
    "\n",
    "    avg_loss = running_loss / len(loader)\n",
    "    accuracy = correct / total\n",
    "    all_preds = np.array(all_preds)\n",
    "    all_labels = np.array(all_labels)\n",
    "    conf_matrix = confusion_matrix(all_labels, all_preds)\n",
    "    return avg_loss, accuracy, conf_matrix\n",
    "\n",
    "\n",
    "def preprocess_filename(filename: str, existed: str = \"keep_both\") -> str:\n",
    "    if existed == \"overwrite\":\n",
    "        pass\n",
    "    elif existed == \"keep_both\":\n",
    "        base, ext = os.path.splitext(filename)\n",
    "        cnt = 1\n",
    "        while os.path.exists(filename):\n",
    "            filename = f\"{base}-{cnt}{ext}\"\n",
    "            cnt += 1\n",
    "    elif existed == \"raise\" and os.path.exists(filename):\n",
    "        raise FileExistsError(f\"{filename} already exists.\")\n",
    "    else:\n",
    "        raise ValueError(f\"Unknown value for 'existed': {existed}\")\n",
    "    return filename\n",
    "\n",
    "\n",
    "def plot_loss_accuracy(\n",
    "    train_loss, train_acc, val_loss, val_acc, filename=\"loss_accuracy.png\"\n",
    "):\n",
    "\n",
    "    fig, (ax1, ax2) = plt.subplots(1, 2)\n",
    "\n",
    "    ax1.set_xlabel(\"Epoch\")\n",
    "    ax1.set_ylabel(\"Loss\")\n",
    "    ax1.plot(train_loss, color=\"tab:blue\")\n",
    "    ax1.plot(val_loss, color=\"tab:red\")\n",
    "    ax1.legend([\"Training\", \"Validation\"])\n",
    "    ax1.set_title(\"Loss\")\n",
    "\n",
    "    ax2.set_xlabel(\"Epoch\")\n",
    "    ax2.set_ylabel(\"Accuracy\")\n",
    "    ax2.plot(train_acc, color=\"tab:blue\")\n",
    "    ax2.plot(val_acc, color=\"tab:red\")\n",
    "    ax2.legend([\"Training\", \"Validation\"])\n",
    "    ax2.set_title(\"Accuracy\")\n",
    "\n",
    "    fig.tight_layout()\n",
    "    filename = preprocess_filename(filename)\n",
    "    os.makedirs(os.path.dirname(filename), exist_ok=True)\n",
    "    plt.savefig(filename)\n",
    "    print(f\"Plot saved at {filename}\")\n",
    "\n",
    "\n",
    "def plot_confusion_matrix(conf_matrix, filename=\"conf_matrix.png\"):\n",
    "    classes = [\n",
    "        \"airplane\",\n",
    "        \"automobile\",\n",
    "        \"bird\",\n",
    "        \"cat\",\n",
    "        \"deer\",\n",
    "        \"dog\",\n",
    "        \"frog\",\n",
    "        \"horse\",\n",
    "        \"ship\",\n",
    "        \"truck\",\n",
    "    ]\n",
    "    plt.figure(figsize=(10, 8))\n",
    "    sns.heatmap(\n",
    "        conf_matrix,\n",
    "        annot=True,\n",
    "        fmt=\"d\",\n",
    "        cmap=\"Blues\",\n",
    "        xticklabels=classes,\n",
    "        yticklabels=classes,\n",
    "    )\n",
    "    plt.xlabel(\"Predicted\")\n",
    "    plt.ylabel(\"True\")\n",
    "    plt.title(\"Confusion Matrix for CIFAR-10 Classification\")\n",
    "    plt.tight_layout()\n",
    "\n",
    "    filename = preprocess_filename(filename)\n",
    "    plt.savefig(filename)\n",
    "    print(f\"Confusion matrix saved to {filename}\")\n",
    "\n",
    "\n",
    "def save_model(\n",
    "    model, filename: str, verbose: bool = True, existed: str = \"keep_both\"\n",
    ") -> None:\n",
    "    filename = preprocess_filename(filename, existed)\n",
    "\n",
    "    os.makedirs(os.path.dirname(filename), exist_ok=True)\n",
    "    torch.save(model.state_dict(), filename)\n",
    "    if verbose:\n",
    "        print(f\"Model saved at {filename} ({os.path.getsize(filename) / 1e6} MB)\")\n",
    "    else:\n",
    "        print(f\"Model saved at {filename}\")\n",
    "\n",
    "\n",
    "def load_model(\n",
    "    model, filename: str, qconfig=None, fuse_modules: bool = False, verbose: bool = True\n",
    ") -> torch.nn.Module:\n",
    "    if fuse_modules and hasattr(model, \"fuse_modules\"):\n",
    "        print(\"Fusing modules\")\n",
    "        model.fuse_modules()\n",
    "    else:\n",
    "        print(\"Model does not have 'fuse_modules' method. Skipping fusion.\")\n",
    "\n",
    "    if qconfig is not None:\n",
    "        model = tq.QuantWrapper(model)\n",
    "        model.qconfig = qconfig\n",
    "        tq.prepare(model, inplace=True)\n",
    "        tq.convert(model, inplace=True)\n",
    "\n",
    "    device = DEFAULT_DEVICE if qconfig is None else \"cpu\"\n",
    "    model.load_state_dict(torch.load(filename, map_location=device))\n",
    "\n",
    "    if verbose:\n",
    "        print(f\"Model loaded from {filename} ({os.path.getsize(filename) / 1e6} MB)\")\n",
    "    return model\n",
    "\n",
    "\n",
    "def reset_seed(seed: int = 42):\n",
    "    torch.manual_seed(seed)\n",
    "    np.random.seed(seed)\n",
    "    torch.backends.cudnn.deterministic = True\n",
    "    torch.backends.cudnn.benchmark = False\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f7cec419",
   "metadata": {},
   "source": [
    "### VGG\n",
    "\n",
    "#### Implement your model based on the given model architecture in the lab material.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "09525dfc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "VGG(\n",
      "  (conv1): Sequential(\n",
      "    (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (2): ReLU(inplace=True)\n",
      "    (3): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (4): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (5): ReLU(inplace=True)\n",
      "    (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "  )\n",
      "  (conv2): Sequential(\n",
      "    (0): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (2): ReLU(inplace=True)\n",
      "    (3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (5): ReLU(inplace=True)\n",
      "    (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "  )\n",
      "  (conv3): Sequential(\n",
      "    (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (2): ReLU(inplace=True)\n",
      "    (3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (5): ReLU(inplace=True)\n",
      "    (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "  )\n",
      "  (conv4): Sequential(\n",
      "    (0): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (2): ReLU(inplace=True)\n",
      "  )\n",
      "  (conv5): Sequential(\n",
      "    (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
      "    (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
      "    (2): ReLU(inplace=True)\n",
      "    (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "  )\n",
      "  (fc6): Sequential(\n",
      "    (0): Linear(in_features=1024, out_features=256, bias=True)\n",
      "    (1): ReLU(inplace=True)\n",
      "  )\n",
      "  (fc7): Sequential(\n",
      "    (0): Linear(in_features=256, out_features=128, bias=True)\n",
      "    (1): ReLU(inplace=True)\n",
      "  )\n",
      "  (fc8): Linear(in_features=128, out_features=10, bias=True)\n",
      ")\n",
      "----------------------------------------------------------------\n",
      "        Layer (type)               Output Shape         Param #\n",
      "================================================================\n",
      "            Conv2d-1           [-1, 32, 32, 32]             896\n",
      "       BatchNorm2d-2           [-1, 32, 32, 32]              64\n",
      "              ReLU-3           [-1, 32, 32, 32]               0\n",
      "            Conv2d-4           [-1, 32, 32, 32]           9,248\n",
      "       BatchNorm2d-5           [-1, 32, 32, 32]              64\n",
      "              ReLU-6           [-1, 32, 32, 32]               0\n",
      "         MaxPool2d-7           [-1, 32, 16, 16]               0\n",
      "            Conv2d-8           [-1, 64, 16, 16]          18,496\n",
      "       BatchNorm2d-9           [-1, 64, 16, 16]             128\n",
      "             ReLU-10           [-1, 64, 16, 16]               0\n",
      "           Conv2d-11           [-1, 64, 16, 16]          36,928\n",
      "      BatchNorm2d-12           [-1, 64, 16, 16]             128\n",
      "             ReLU-13           [-1, 64, 16, 16]               0\n",
      "        MaxPool2d-14             [-1, 64, 8, 8]               0\n",
      "           Conv2d-15            [-1, 128, 8, 8]          73,856\n",
      "      BatchNorm2d-16            [-1, 128, 8, 8]             256\n",
      "             ReLU-17            [-1, 128, 8, 8]               0\n",
      "           Conv2d-18            [-1, 128, 8, 8]         147,584\n",
      "      BatchNorm2d-19            [-1, 128, 8, 8]             256\n",
      "             ReLU-20            [-1, 128, 8, 8]               0\n",
      "        MaxPool2d-21            [-1, 128, 4, 4]               0\n",
      "           Conv2d-22            [-1, 256, 4, 4]         295,168\n",
      "      BatchNorm2d-23            [-1, 256, 4, 4]             512\n",
      "             ReLU-24            [-1, 256, 4, 4]               0\n",
      "           Conv2d-25            [-1, 256, 4, 4]         590,080\n",
      "      BatchNorm2d-26            [-1, 256, 4, 4]             512\n",
      "             ReLU-27            [-1, 256, 4, 4]               0\n",
      "        MaxPool2d-28            [-1, 256, 2, 2]               0\n",
      "           Linear-29                  [-1, 256]         262,400\n",
      "             ReLU-30                  [-1, 256]               0\n",
      "           Linear-31                  [-1, 128]          32,896\n",
      "             ReLU-32                  [-1, 128]               0\n",
      "           Linear-33                   [-1, 10]           1,290\n",
      "================================================================\n",
      "Total params: 1,470,762\n",
      "Trainable params: 1,470,762\n",
      "Non-trainable params: 0\n",
      "----------------------------------------------------------------\n",
      "Input size (MB): 0.01\n",
      "Forward/backward pass size (MB): 2.94\n",
      "Params size (MB): 5.61\n",
      "Estimated Total Size (MB): 8.56\n",
      "----------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.ao.quantization as tq\n",
    "import torch.nn.functional as F\n",
    "\n",
    "\n",
    "class VGG(nn.Module):\n",
    "    def __init__(self, in_channels=3, in_size=32, num_classes=10) -> None:\n",
    "        super(VGG, self).__init__()\n",
    "        \n",
    "        # Conv1 (2 layers)\n",
    "        self.conv1 = nn.Sequential(\n",
    "            nn.Conv2d(in_channels, 32, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(32),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.Conv2d(32, 32, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(32),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2)  # 32×32 -> 16×16\n",
    "        )\n",
    "        \n",
    "        # Conv2 (2 layers)\n",
    "        self.conv2 = nn.Sequential(\n",
    "            nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(64),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(64),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2)  # 16×16 -> 8×8\n",
    "        )\n",
    "        \n",
    "        # Conv3 (2 layers)\n",
    "        self.conv3 = nn.Sequential(\n",
    "            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(128),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(128),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2)  # 8×8 -> 4×4\n",
    "        )\n",
    "        \n",
    "        # Conv4 (Dilated, 1 layer)\n",
    "        self.conv4 = nn.Sequential(\n",
    "            nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.ReLU(inplace=True)\n",
    "        )\n",
    "        \n",
    "        # Conv5 (1 layer)\n",
    "        self.conv5 = nn.Sequential(\n",
    "            nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.ReLU(inplace=True),\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2)  # 4×4 -> 2×2\n",
    "        )\n",
    "        \n",
    "        # Fully Connected Layers\n",
    "        fmap_size = in_size // 16  # 32 -> 16 -> 8 -> 4 -> 2 (4 次 MaxPool)\n",
    "        self.fc6 = nn.Sequential(\n",
    "            nn.Linear(256 * fmap_size * fmap_size, 256),  # 256×2×2 = 1024\n",
    "            nn.ReLU(inplace=True)\n",
    "        )\n",
    "        self.fc7 = nn.Sequential(\n",
    "            nn.Linear(256, 128),\n",
    "            nn.ReLU(inplace=True)\n",
    "        )\n",
    "        self.fc8 = nn.Linear(128, num_classes)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)  # 32×32 -> 16×16\n",
    "        x = self.conv2(x)  # 16×16 -> 8×8\n",
    "        x = self.conv3(x)  # 8×8 -> 4×4\n",
    "        x = self.conv4(x)  # 4×4 -> 4×4\n",
    "        x = self.conv5(x)  # 4×4 -> 2×2\n",
    "        x = torch.flatten(x, start_dim=1)  # Flatten: (batch_size, 1024)\n",
    "        x = self.fc6(x)  # 1024 -> 256\n",
    "        x = self.fc7(x)  # 256 -> 128\n",
    "        x = self.fc8(x)  # 128 -> 10\n",
    "        return x\n",
    "        \n",
    "    def fuse_modules(self):\n",
    "        # fuse conv1 兩組\n",
    "        self.eval()\n",
    "        tq.fuse_modules(self.conv1, [['0', '1', '2'], ['3', '4', '5']], inplace=True)\n",
    "        \n",
    "        # fuse conv2\n",
    "        tq.fuse_modules(self.conv2, [['0', '1', '2'], ['3', '4', '5']], inplace=True)\n",
    "\n",
    "        # fuse conv3\n",
    "        tq.fuse_modules(self.conv3, [['0', '1', '2'], ['3', '4', '5']], inplace=True)\n",
    "\n",
    "        # fuse conv4 (只有一組)\n",
    "        tq.fuse_modules(self.conv4, [['0', '1', '2']], inplace=True)\n",
    "\n",
    "        # fuse conv5\n",
    "        tq.fuse_modules(self.conv5, [['0', '1', '2']], inplace=True)\n",
    "\n",
    "        # fuse 全連接層\n",
    "        tq.fuse_modules(self.fc6, [['0', '1']], inplace=True)\n",
    "        tq.fuse_modules(self.fc7, [['0', '1']], inplace=True)\n",
    "\n",
    "        \n",
    "            \n",
    "if __name__ == \"__main__\":\n",
    "    model = VGG()\n",
    "    inputs = torch.randn(1, 3, 32, 32)\n",
    "    print(model)\n",
    "\n",
    "    from torchsummary import summary\n",
    "\n",
    "    summary(model, (3, 32, 32), device=\"cpu\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d1d0dc0",
   "metadata": {},
   "source": [
    "### QConfig\n",
    "#### Quantization scheme\n",
    "- Write your QConfig Observer inorder to do PTQ\n",
    "- Use Power-of-Two uniform/scale, symmetric quantization to quantize model.\n",
    "- Finish qconfig observer for PTQ calibration.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "31eeb229",
   "metadata": {},
   "outputs": [],
   "source": [
    "from enum import Enum\n",
    "import math\n",
    "\n",
    "import torch\n",
    "import torch.ao.quantization as tq\n",
    "\n",
    "\n",
    "class PowerOfTwoObserver(tq.MinMaxObserver):\n",
    "    \"\"\"\n",
    "    Observer module for power-of-two quantization (dyadic quantization with b = 1).\n",
    "    \"\"\"\n",
    "\n",
    "    def scale_approximate(self, scale: float, max_shift_amount=8) -> float:\n",
    "        if scale == 0:\n",
    "            return 0.0\n",
    "        exponent = round(math.log(scale, 2))\n",
    "        exponent = max(-max_shift_amount, min(exponent, 0))\n",
    "        return 2 ** exponent\n",
    "        \n",
    "    def calculate_qparams(self):\n",
    "        \"\"\"Calculates the quantization parameters with scale as power of two.\"\"\"\n",
    "        min_val, max_val = self.min_val.item(), self.max_val.item()\n",
    "        if self.dtype == torch.qint8:\n",
    "            qmin, qmax = -127, 127\n",
    "            zero_point = 0\n",
    "        elif self.dtype == torch.quint8:\n",
    "            qmin, qmax = 0, 255\n",
    "            zero_point = 128\n",
    "        else:\n",
    "            raise ValueError(\"Unsupported dtype\")\n",
    "        \n",
    "        max_abs = 2 * max(abs(min_val), abs(max_val))\n",
    "        # 避免除以0或inf\n",
    "        if max_abs == 0:\n",
    "            scale = 0\n",
    "        elif max_abs == float(\"inf\"):\n",
    "            scale = 1\n",
    "        else:\n",
    "            scale = max_abs / (qmax-qmin)\n",
    "        scale = self.scale_approximate(scale)\n",
    "        scale = torch.tensor(scale, dtype=torch.float32)\n",
    "        zero_point = torch.tensor(zero_point, dtype=torch.int64)\n",
    "        zero_point = torch.clamp(zero_point, qmin, qmax)\n",
    "        # print(f\"scale: {scale.item()}\") \n",
    "        return scale, zero_point\n",
    "    \n",
    "class CustomQConfig(Enum):\n",
    "    POWER2 = tq.QConfig(\n",
    "        activation=PowerOfTwoObserver.with_args(\n",
    "            dtype=torch.quint8, qscheme=torch.per_tensor_symmetric\n",
    "        ),\n",
    "        weight=PowerOfTwoObserver.with_args(\n",
    "            dtype=torch.qint8, qscheme=torch.per_tensor_symmetric\n",
    "        ),\n",
    "    )\n",
    "    DEFAULT = None\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aeeb7099",
   "metadata": {},
   "source": [
    "### Model Training\n",
    "\n",
    "- Set hyper parameter for training.\n",
    "- Record the number of epochs and the accuracy in the results.\n",
    "- Plot the accuracy and loss.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5b181bba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Files already downloaded and verified\n",
      "Files already downloaded and verified\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "bb35da2cdb914434a57578bcd6b6eebe",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Epochs:   0%|          | 0/50 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a3a570208b3440e3a791cc39550c7ea8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7b3d7f2bdda74a3ab987cf9bc9e983f5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  1/50  lr=1.00e-01, train_loss=1.9539, val_loss=1.9951, train_acc=0.2861, val_acc=0.3176\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "860abe6c85b14ff58d74d7d71512c4fc",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "557f700965fc4dfd9800b157c6426722",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  2/50  lr=9.99e-02, train_loss=1.6843, val_loss=1.7156, train_acc=0.4442, val_acc=0.4602\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "43a2dee2e3d9435ba3073082eea7644b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5317c22dd79f4cf58570a827b0800972",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  3/50  lr=9.96e-02, train_loss=1.5240, val_loss=1.5128, train_acc=0.5345, val_acc=0.5452\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6618387002da45f2aab8aa3eb5d8db70",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4cbcb4ca76fd473a8bef4981c26dd6c8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  4/50  lr=9.91e-02, train_loss=1.4391, val_loss=1.6168, train_acc=0.5799, val_acc=0.5236\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3cafd81c2ac2475e82cd7a20358c6add",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7a65b99f9f8a402e9da47a150a21ea89",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  5/50  lr=9.84e-02, train_loss=1.3623, val_loss=1.7862, train_acc=0.6184, val_acc=0.4640\n",
      "No improvement for 2 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f854c7fb70ea43588c77301de9067a28",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7e3c0dc6c74348a39955c307a8f0dcf4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  6/50  lr=9.76e-02, train_loss=1.3179, val_loss=1.5912, train_acc=0.6422, val_acc=0.5520\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "513740bbf84748debcb51ff2398110e1",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "76184a82d4b24d5586f12e6731f36db4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  7/50  lr=9.65e-02, train_loss=1.2801, val_loss=1.7651, train_acc=0.6605, val_acc=0.5032\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "87d42f2d256c4d7888d6bfdabb1b553d",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "28a8f5909e3e41738cc9b8b884f0569a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  8/50  lr=9.52e-02, train_loss=1.2422, val_loss=1.4203, train_acc=0.6778, val_acc=0.6074\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f597e02570a24e4d9e18896c2ced0362",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "942ae99bfd584df6b8327dd60d1098e5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch  9/50  lr=9.38e-02, train_loss=1.2206, val_loss=1.4079, train_acc=0.6875, val_acc=0.6076\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "71804d2ff30547b29ca0f91f442b66f0",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "03751de6519d4c61bd0809db73745e7b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 10/50  lr=9.22e-02, train_loss=1.2011, val_loss=1.3217, train_acc=0.6969, val_acc=0.6498\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "795ccf09eb0e4bd4b080dde11ee8ef4a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a493c5d0d28345ce8668506e06dce58b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 11/50  lr=9.05e-02, train_loss=1.1875, val_loss=1.3528, train_acc=0.7022, val_acc=0.6348\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c6883f4e282542b49436ff48debd81fa",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f887fef2fd934cb3912efc045c1f72a2",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 12/50  lr=8.85e-02, train_loss=1.1714, val_loss=1.3506, train_acc=0.7096, val_acc=0.6348\n",
      "No improvement for 2 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "2ba7d1efb2f24271b76f1d5c8b48e6fe",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c7ee7b4cba9e4252999f12adbbf879c0",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 13/50  lr=8.64e-02, train_loss=1.1593, val_loss=1.2253, train_acc=0.7136, val_acc=0.6786\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "db7e32f4e26c407199e0d743139fb624",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "01a986c70cb94a23bfd505f79592ccc3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 14/50  lr=8.42e-02, train_loss=1.1402, val_loss=1.4282, train_acc=0.7228, val_acc=0.6080\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "d11a0350f30a46c3afd47ba954e9862c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "cff95d85549f4145b7a7d88674ce695b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 15/50  lr=8.19e-02, train_loss=1.1365, val_loss=1.4340, train_acc=0.7235, val_acc=0.6220\n",
      "No improvement for 2 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4e49357bca094004b25735960b73fa03",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "34da6703e7b54531a12eb1fd8473d310",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 16/50  lr=7.94e-02, train_loss=1.1225, val_loss=1.2844, train_acc=0.7313, val_acc=0.6614\n",
      "No improvement for 3 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "95b3de74987e4be6a332a7f32847de0e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "cee8a970785642a0a8613a6318caf23a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 17/50  lr=7.68e-02, train_loss=1.1098, val_loss=1.3218, train_acc=0.7380, val_acc=0.6626\n",
      "No improvement for 4 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "81c281a9e5a7427a8f88be442d4f0955",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9d971af5223142e1bfa228099af811e3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 18/50  lr=7.41e-02, train_loss=1.0992, val_loss=1.2942, train_acc=0.7408, val_acc=0.6524\n",
      "No improvement for 5 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b21de7f328a7418a9cdde8fd59504073",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3906cbd095694357b817a422d2fe3517",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 19/50  lr=7.13e-02, train_loss=1.0858, val_loss=1.3201, train_acc=0.7475, val_acc=0.6532\n",
      "No improvement for 6 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "63a3dfa1a430435eb5e0b4d046953007",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4449c569842b4f61a642a39ffcc9ff0d",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 20/50  lr=6.84e-02, train_loss=1.0741, val_loss=1.2126, train_acc=0.7503, val_acc=0.7004\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "063e6258f4d9437bb3030fa4f691fce8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4d7c284c0c9e45cf9b369f4628d9cb4c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 21/50  lr=6.55e-02, train_loss=1.0574, val_loss=1.2219, train_acc=0.7581, val_acc=0.6924\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f4fec1d1acac47299e35695f00d96e59",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "334a79a2e6ec4446babc903564d75ed5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 22/50  lr=6.24e-02, train_loss=1.0435, val_loss=1.1392, train_acc=0.7650, val_acc=0.7264\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a6f80ab4d8044275beb6d4215db6d372",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b7950872ede447ad800429a0d2b5af29",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 23/50  lr=5.94e-02, train_loss=1.0410, val_loss=1.1353, train_acc=0.7665, val_acc=0.7162\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "822a6a48f72f4d4ebd4d7488a70c7daa",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "21297b417ad344b5b2a9e9bb77680f2d",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 24/50  lr=5.63e-02, train_loss=1.0215, val_loss=1.0886, train_acc=0.7746, val_acc=0.7410\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1e31a937f4d14c8d874ea6938fc00dcc",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "64a1173833684278aeb036ab9f372091",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 25/50  lr=5.31e-02, train_loss=1.0150, val_loss=1.1552, train_acc=0.7772, val_acc=0.7134\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3777cb93e71f443b9ce8ea759ac20cd4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c741ca960f464b98a69eaa95c59b20a9",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 26/50  lr=5.00e-02, train_loss=1.0041, val_loss=1.0706, train_acc=0.7797, val_acc=0.7526\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "615bddd954f946eea667d0608320429c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1876e5d31b4b4fcea7ceda4d9c5db22d",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 27/50  lr=4.69e-02, train_loss=0.9872, val_loss=1.1310, train_acc=0.7875, val_acc=0.7264\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fcd99979163e43f8b52c34cfb409839c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b0bdc617a6b240bcb08771f3a43ce2df",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 28/50  lr=4.37e-02, train_loss=0.9726, val_loss=1.0791, train_acc=0.7947, val_acc=0.7482\n",
      "No improvement for 2 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e3b609a64a144f27839c822365f81dae",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "62d9cf2d02b24a0d8f168b1730d4e12c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 29/50  lr=4.06e-02, train_loss=0.9585, val_loss=1.0840, train_acc=0.8018, val_acc=0.7456\n",
      "No improvement for 3 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "bef5a864fb2a452aa90a468a4f0ae233",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c1589ae18dcf4a7eb29fba3145da7481",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 30/50  lr=3.76e-02, train_loss=0.9436, val_loss=1.1237, train_acc=0.8051, val_acc=0.7298\n",
      "No improvement for 4 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b903e21b606e4a0aaac36bb30ea4c012",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "723fba94aae1446bae015deea7b6eb4f",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 31/50  lr=3.45e-02, train_loss=0.9372, val_loss=0.9873, train_acc=0.8100, val_acc=0.7876\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0ff53e61adf242398f012464fe035b5c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "19852c3571584b6e93dff1bd64d739f9",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 32/50  lr=3.16e-02, train_loss=0.9148, val_loss=1.0005, train_acc=0.8196, val_acc=0.7840\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6aa82657c71449b5bebc4466a634c05e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6d9e9d1e7b6e4cdaaa606e792d7b1e15",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 33/50  lr=2.87e-02, train_loss=0.8999, val_loss=1.0198, train_acc=0.8240, val_acc=0.7734\n",
      "No improvement for 2 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0be322ceba8e4afd9329bdbd98596a93",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ea42ed0a18104fec871003f91f1455e9",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 34/50  lr=2.59e-02, train_loss=0.8880, val_loss=1.0295, train_acc=0.8291, val_acc=0.7686\n",
      "No improvement for 3 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3c540d78b9af49c2bc15151845770395",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "57a32ab3e8b2484e834830c9922ca3e4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 35/50  lr=2.32e-02, train_loss=0.8742, val_loss=0.9855, train_acc=0.8352, val_acc=0.7874\n",
      "No improvement for 4 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "14514d86044e43218f58114a59574bab",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3a11d16c9c5148d7adac5ce6ca3586c0",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 36/50  lr=2.06e-02, train_loss=0.8604, val_loss=0.9607, train_acc=0.8428, val_acc=0.7938\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5e87ebf894ed448a8299ba7eb7668b20",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "310ec765bca84eaa8612387201df4163",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 37/50  lr=1.81e-02, train_loss=0.8382, val_loss=0.9468, train_acc=0.8508, val_acc=0.8100\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c4806758c4c54e6dbef6a6ecb1ce5955",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "d3a06e88c0c64ae89c2e7a4fc520c197",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 38/50  lr=1.58e-02, train_loss=0.8206, val_loss=0.8839, train_acc=0.8610, val_acc=0.8282\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e8f87078de434b328fe78c110825bd34",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "55d23b4bf12642f99318a14aafbecd11",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 39/50  lr=1.36e-02, train_loss=0.8080, val_loss=0.9125, train_acc=0.8648, val_acc=0.8178\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5edac263e20548c8820901d1e7804d3d",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a3a2ef65cb8d4af78e4b667e756bab42",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 40/50  lr=1.15e-02, train_loss=0.7893, val_loss=0.8707, train_acc=0.8722, val_acc=0.8374\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6c5c01facb354ca8a0f7a6ba23e36dae",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6537a46d0ab24d1f951c97bda92bf41c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 41/50  lr=9.55e-03, train_loss=0.7668, val_loss=0.8626, train_acc=0.8844, val_acc=0.8454\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b11150859ef14c7fb04ea3b723156b79",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9fa1e0e73da34585a759b9bf568e2cb4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 42/50  lr=7.78e-03, train_loss=0.7543, val_loss=0.8522, train_acc=0.8881, val_acc=0.8442\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "95ed66a8d70d48168a4029a760891fd1",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "05efcfe882c54c3282ea14d164b7d97a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 43/50  lr=6.19e-03, train_loss=0.7333, val_loss=0.8327, train_acc=0.8984, val_acc=0.8558\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "dc87f388fa6d4298946b8320c88f3109",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "233709a3982d40dbbb008da2e7eb3441",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 44/50  lr=4.76e-03, train_loss=0.7207, val_loss=0.8076, train_acc=0.9033, val_acc=0.8656\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7b65892f2bee4fe7b20d1af9dde79f4e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b7251bfa192b413aac3006e9caf90abf",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 45/50  lr=3.51e-03, train_loss=0.7067, val_loss=0.8038, train_acc=0.9108, val_acc=0.8682\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e7e9e767041643f2a7fe348c4a2277fa",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6d9ded0e386e4c3c8ff00e941de3f7b3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 46/50  lr=2.45e-03, train_loss=0.6924, val_loss=0.8054, train_acc=0.9163, val_acc=0.8694\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7a7759dbd492450492b0d994763f4fd7",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0ce9e677900b4d45a0d6f0c32ee8207e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 47/50  lr=1.57e-03, train_loss=0.6825, val_loss=0.7858, train_acc=0.9219, val_acc=0.8672\n",
      "No improvement for 1 epoch(s).\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "2bc2373ed39a4c5da3b2d86ea1f1b7af",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5d05b2f5deb54e4bb0fbf8a97ccfe7c7",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 48/50  lr=8.87e-04, train_loss=0.6725, val_loss=0.7834, train_acc=0.9257, val_acc=0.8748\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b2d9daa552264c0aba9083fb4162ccd7",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c810cd8194c845ceb96b7fce44f0736a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 49/50  lr=3.95e-04, train_loss=0.6703, val_loss=0.7873, train_acc=0.9280, val_acc=0.8750\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "544e32f694f240ebba73a6ea1935f28c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Training:   0%|          | 0/704 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9fb0b277a612400195dd8e3c472d2f5f",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/79 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 50/50  lr=9.97e-05, train_loss=0.6681, val_loss=0.7855, train_acc=0.9278, val_acc=0.8774\n",
      "Model saved at weights/cifar10/vgg.pt (5.907156 MB)\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a5817b6120174b9c837e2e364c70dcfb",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/157 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test: loss=0.7097, accuracy=0.9111\n",
      "Model size: 5.91 MB\n",
      "Plot saved at figure/cifar10/vgg-9.png\n",
      "Time: 1928.42s\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import os\n",
    "import time\n",
    "\n",
    "import torch\n",
    "from torch import nn, optim\n",
    "from tqdm.notebook import tqdm\n",
    "\n",
    "reset_seed(10)\n",
    "\n",
    "\n",
    "def train_one_epoch(model, loader, criterion, optimizer, device=DEFAULT_DEVICE):\n",
    "    running_loss = 0\n",
    "    total, correct = 0, 0\n",
    "\n",
    "    loop = tqdm(loader, desc=\"Training\", leave=True)\n",
    "\n",
    "    for images, labels in loop:\n",
    "        images, labels = images.to(device), labels.to(device)\n",
    "        optimizer.zero_grad()\n",
    "        output = model(images)\n",
    "        loss = criterion(output, labels)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "\n",
    "        running_loss += loss.item()\n",
    "        predicted = torch.argmax(output, 1)\n",
    "        total += labels.size(0)\n",
    "        correct += (predicted == labels).sum().item()\n",
    "\n",
    "        loop.set_postfix(\n",
    "            loss=running_loss / (total / images.shape[0]), accuracy=correct / total\n",
    "        )\n",
    "\n",
    "    avg_loss = running_loss / len(loader)\n",
    "    accuracy = correct / total\n",
    "    return avg_loss, accuracy\n",
    "\n",
    "\n",
    "def train(\n",
    "    model,\n",
    "    trainloader,\n",
    "    valloader,\n",
    "    criterion,\n",
    "    optimizer,\n",
    "    scheduler=None,\n",
    "    epochs=1,\n",
    "    save_path=None,\n",
    "    early_stop_patience=5,\n",
    "    device=DEFAULT_DEVICE,\n",
    "):\n",
    "    model = model.to(device)\n",
    "\n",
    "    train_loss, train_acc = [], []\n",
    "    val_loss, val_acc = [], []\n",
    "\n",
    "    best_val_acc = 0.0\n",
    "    early_stop_count = 0\n",
    "\n",
    "    epoch_loop = tqdm(range(epochs), desc=\"Epochs\", leave=True)\n",
    "\n",
    "    for epoch in epoch_loop:\n",
    "        model.train()\n",
    "        _train_loss, _train_acc = train_one_epoch(\n",
    "            model, trainloader, criterion, optimizer\n",
    "        )\n",
    "        train_loss.append(_train_loss)\n",
    "        train_acc.append(_train_acc)\n",
    "\n",
    "        model.eval()\n",
    "        _val_loss, _val_acc, _ = evaluate(model, valloader, criterion)\n",
    "        val_loss.append(_val_loss)\n",
    "        val_acc.append(_val_acc)\n",
    "\n",
    "        print(f\"Epoch {epoch + 1:2d}/{epochs}\", end=\"  \")\n",
    "        if scheduler is not None:\n",
    "            print(f\"lr={scheduler.get_last_lr()[0]:.2e}\", end=\", \")\n",
    "        print(f\"train_loss={_train_loss:.4f}, val_loss={_val_loss:.4f}\", end=\", \")\n",
    "        print(f\"train_acc={_train_acc:.4f}, val_acc={_val_acc:.4f}\")\n",
    "\n",
    "\n",
    "        # 若當前驗證準確率超越最佳值則儲存模型\n",
    "        if _val_acc >= best_val_acc:\n",
    "            best_val_acc = _val_acc\n",
    "            early_stop_count = 0  # 重置 patience 計數器\n",
    "            if _val_acc >= max(val_acc):\n",
    "                save_model(model, save_path, existed=\"overwrite\")\n",
    "        else:\n",
    "            early_stop_count += 1\n",
    "            print(f\"No improvement for {early_stop_count} epoch(s).\")\n",
    "\n",
    "        # 若超過 patience，則提前停止訓練\n",
    "        if early_stop_count >= early_stop_patience:\n",
    "            print(f\"Early stopping triggered after {epoch+1} epochs.\")\n",
    "            break\n",
    "        \n",
    "\n",
    "        if scheduler is not None:\n",
    "            scheduler.step()\n",
    "\n",
    "        epoch_loop.set_postfix(\n",
    "            train_loss=_train_loss,\n",
    "            val_loss=_val_loss,\n",
    "            train_acc=_train_acc,\n",
    "            val_acc=_val_acc,\n",
    "        )\n",
    "\n",
    "    return train_loss, train_acc, val_loss, val_acc\n",
    "\n",
    "\n",
    "def main(epochs, network, dataset, name=None):\n",
    "    dataset = dataset.lower()\n",
    "    if name is None:\n",
    "        name = f\"{dataset}/{network.__name__.lower()}\"\n",
    "\n",
    "    t = time.time()\n",
    "    trainloader, valloader, testloader = DATALOADERS[dataset](batch_size=64)\n",
    "    in_channels, in_size = trainloader.dataset[0][0].shape[:2]\n",
    "    model = network(in_channels, in_size).to(DEFAULT_DEVICE)\n",
    "    #########Implement your code here##########\n",
    "    #Loss function\n",
    "    criterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n",
    "    #Optimizer\n",
    "    optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)\n",
    "    #Scheduler\n",
    "    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs, eta_min=1e-6)\n",
    "    ##########################################\n",
    "    train_loss, train_acc, val_loss, val_acc = train(\n",
    "        model,\n",
    "        trainloader,\n",
    "        valloader,\n",
    "        criterion,\n",
    "        optimizer,\n",
    "        scheduler,\n",
    "        epochs,\n",
    "        save_path=f\"weights/{name}.pt\",\n",
    "        early_stop_patience=10,  # 若驗證準確率 10 個 epoch 無提升則提前結束\n",
    "    )\n",
    "\n",
    "    test_loss, test_accuracy, _ = evaluate(model.eval(), testloader, criterion)\n",
    "    print(f\"Test: loss={test_loss:.4f}, accuracy={test_accuracy:.4f}\")\n",
    "    print(f\"Model size: {os.path.getsize(f'weights/{name}.pt') / 1e6:.2f} MB\")\n",
    "\n",
    "    plot_loss_accuracy(train_loss, train_acc, val_loss, val_acc, f\"figure/{name}.png\")\n",
    "    print(f\"Time: {time.time() - t:.2f}s\")\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    for network in [VGG]:\n",
    "        \"\"\" You can adjust the number of epochs \"\"\"\n",
    "        EPOCHS = 50\n",
    "        main(epochs=EPOCHS, network=network, dataset=\"cifar10\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21abdd6e",
   "metadata": {},
   "source": [
    "### PTQ on VGG Model\n",
    "\n",
    "#### You can refer to 'Quantization in Practice' in the lab material.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "11c3220e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Files already downloaded and verified\n",
      "Files already downloaded and verified\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\Administrator\\AppData\\Local\\Temp\\ipykernel_26788\\1024425284.py:31: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  fmodel.load_state_dict(torch.load(model_path))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Quantization backend: QConfig(activation=functools.partial(<class '__main__.PowerOfTwoObserver'>, dtype=torch.quint8, qscheme=torch.per_tensor_symmetric){}, weight=functools.partial(<class '__main__.PowerOfTwoObserver'>, dtype=torch.qint8, qscheme=torch.per_tensor_symmetric){})\n",
      "Model saved at weights/cifar10/vgg-power2.pt (1.48893 MB)\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import torch\n",
    "from torch import nn\n",
    "import torch.ao.quantization as tq\n",
    "\n",
    "reset_seed(0)\n",
    "\n",
    "#########Implement your code here##########\n",
    "\"\"\" Calibrate Method \"\"\"\n",
    "def calibrate(model, loader, device=DEFAULT_DEVICE):\n",
    "    model.eval().to(device)   \n",
    "    for x, _ in loader:       \n",
    "        model(x.to(device))  \n",
    "        break                 \n",
    "\n",
    "###########################################\n",
    "\n",
    "def main(network, verbose=True):\n",
    "\n",
    "    #########Implement your code here##########\n",
    "    \"\"\" Calibration \"\"\"\n",
    "\n",
    "    \"\"\" Calibration Data \"\"\"\n",
    "    dataset = 'cifar10'\n",
    "    backend = 'power2'\n",
    "    model_path = 'weights/cifar10/vgg.pt'\n",
    "    *_, test_loader = DATALOADERS[dataset](batch_size=1)\n",
    "    in_channels, in_size = trainloader.dataset[0][0].shape[:2]\n",
    "    \"\"\" Load Pretrained Model \"\"\"\n",
    "    fmodel = network(in_channels, in_size).eval().cpu()\n",
    "    fmodel.load_state_dict(torch.load(model_path))\n",
    "    \"\"\" Fuse Modules \"\"\"\n",
    "    # fmodel = load_model(network(in_channels, in_size), model_path, qconfig = None, fuse_modules= False)\n",
    "    fmodel.fuse_modules()\n",
    "    \"\"\" Configure Quantization \"\"\"\n",
    "    fmodel = tq.QuantWrapper(fmodel)  \n",
    "    fmodel.qconfig = CustomQConfig.POWER2.value\n",
    "    print(f\"Quantization backend: {fmodel.qconfig}\")\n",
    "    \n",
    "    \"\"\" Apply Quantization \"\"\"\n",
    "    tq.prepare(fmodel, inplace=True)\n",
    "    calibrate(fmodel, test_loader, \"cpu\") \n",
    "\n",
    "    \"\"\" Convert Model \"\"\"\n",
    "    tq.convert(fmodel.cpu(), inplace=True)\n",
    "\n",
    "    \"\"\" Save Model \"\"\"\n",
    "    save_model(fmodel, \"weights/cifar10/vgg-power2.pt\", existed=\"overwrite\")\n",
    "    ###########################################\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main(network=VGG, verbose=True)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0691b2ff",
   "metadata": {},
   "source": [
    "### Evaluate Quantized Model\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b270f467",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Files already downloaded and verified\n",
      "Files already downloaded and verified\n",
      "Fusing modules\n",
      "Model loaded from ./weights/cifar10/vgg-power2.pt (1.48893 MB)\n",
      "QuantWrapper(\n",
      "  (quant): Quantize(scale=tensor([0.0156]), zero_point=tensor([128]), dtype=torch.quint8)\n",
      "  (dequant): DeQuantize()\n",
      "  (module): VGG(\n",
      "    (conv1): Sequential(\n",
      "      (0): QuantizedConvReLU2d(3, 32, kernel_size=(3, 3), stride=(1, 1), scale=0.015625, zero_point=128, padding=(1, 1))\n",
      "      (1): Identity()\n",
      "      (2): Identity()\n",
      "      (3): QuantizedConvReLU2d(32, 32, kernel_size=(3, 3), stride=(1, 1), scale=0.015625, zero_point=128, padding=(1, 1))\n",
      "      (4): Identity()\n",
      "      (5): Identity()\n",
      "      (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    )\n",
      "    (conv2): Sequential(\n",
      "      (0): QuantizedConvReLU2d(32, 64, kernel_size=(3, 3), stride=(1, 1), scale=0.015625, zero_point=128, padding=(1, 1))\n",
      "      (1): Identity()\n",
      "      (2): Identity()\n",
      "      (3): QuantizedConvReLU2d(64, 64, kernel_size=(3, 3), stride=(1, 1), scale=0.015625, zero_point=128, padding=(1, 1))\n",
      "      (4): Identity()\n",
      "      (5): Identity()\n",
      "      (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    )\n",
      "    (conv3): Sequential(\n",
      "      (0): QuantizedConvReLU2d(64, 128, kernel_size=(3, 3), stride=(1, 1), scale=0.0078125, zero_point=128, padding=(1, 1))\n",
      "      (1): Identity()\n",
      "      (2): Identity()\n",
      "      (3): QuantizedConvReLU2d(128, 128, kernel_size=(3, 3), stride=(1, 1), scale=0.0078125, zero_point=128, padding=(1, 1))\n",
      "      (4): Identity()\n",
      "      (5): Identity()\n",
      "      (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    )\n",
      "    (conv4): Sequential(\n",
      "      (0): QuantizedConvReLU2d(128, 256, kernel_size=(3, 3), stride=(1, 1), scale=0.0078125, zero_point=128, padding=(1, 1))\n",
      "      (1): Identity()\n",
      "      (2): Identity()\n",
      "    )\n",
      "    (conv5): Sequential(\n",
      "      (0): QuantizedConvReLU2d(256, 256, kernel_size=(3, 3), stride=(1, 1), scale=0.0078125, zero_point=128, padding=(1, 1))\n",
      "      (1): Identity()\n",
      "      (2): Identity()\n",
      "      (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
      "    )\n",
      "    (fc6): Sequential(\n",
      "      (0): QuantizedLinearReLU(in_features=1024, out_features=256, scale=0.015625, zero_point=128, qscheme=torch.per_tensor_affine)\n",
      "      (1): Identity()\n",
      "    )\n",
      "    (fc7): Sequential(\n",
      "      (0): QuantizedLinearReLU(in_features=256, out_features=128, scale=0.015625, zero_point=128, qscheme=torch.per_tensor_affine)\n",
      "      (1): Identity()\n",
      "    )\n",
      "    (fc8): QuantizedLinear(in_features=128, out_features=10, scale=0.03125, zero_point=128, qscheme=torch.per_tensor_affine)\n",
      "  )\n",
      ")\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\Administrator\\AppData\\Local\\Temp\\ipykernel_26788\\2912952992.py:152: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(filename, map_location=device))\n",
      "d:\\anaconda3\\envs\\AoC\\lib\\site-packages\\torch\\_utils.py:383: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly.  To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()\n",
      "  device=storage.device,\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1b9b3016ec834257b45d1cf08c656a47",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Evaluating:   0%|          | 0/10 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test: loss=0.4812, accuracy=0.8917, size=1.48893MB\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from torch import nn\n",
    "import torch.ao.quantization as tq\n",
    "\n",
    "reset_seed(0)\n",
    "\n",
    "def main():\n",
    "    dataset = 'cifar10'\n",
    "    backend = 'power2'\n",
    "    model_path = './weights/cifar10/vgg-power2.pt'\n",
    "\n",
    "    *_, test_loader = DATALOADERS[dataset](batch_size=1000)\n",
    "    in_channels, in_size = test_loader.dataset[0][0].shape[:2]\n",
    "    if backend:\n",
    "        qconfig = CustomQConfig[backend.upper()].value\n",
    "        fuse_modules = True\n",
    "    else:\n",
    "        qconfig = None\n",
    "        fuse_modules = False\n",
    "    model = load_model(VGG(in_channels, in_size), model_path, qconfig=qconfig, fuse_modules=fuse_modules)\n",
    "    print(model)\n",
    "\n",
    "    device = \"cpu\" if backend else DEFAULT_DEVICE\n",
    "    criterion = nn.CrossEntropyLoss()\n",
    "    test_loss, test_accuracy, _ = evaluate(\n",
    "        model.to(device), test_loader, criterion, device=device\n",
    "    )\n",
    "    print(\n",
    "        f\"Test: loss={test_loss:.4f}, accuracy={test_accuracy:.4f}, size={os.path.getsize(model_path) / 1e6}MB\"\n",
    "    )\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47a7ca52",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "AoC",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
